/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System.Linq;
using Nvigorate.Common.Reflection;
using Nvigorate.Data.Query;
using Nvigorate.Extensions;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;

namespace Nvigorate.Relational.Builders
{
    public class UpdateBuilder : PersistBuilder
    {
        protected IQueryObject ConcurrencyCheck
        {
            get
            {
                return IfBlock
                    .Condition(Criterion.Literal(0).EqualTo(QueryValue.Reference("@@ROWCOUNT")))
                    .IfTrue(RaiseError.Message(SourceMap.SourceName, 14, 14));
            }
        }

        protected bool HasUpdates
        {
            get
            {
                return Persistable.IsDirty &&
                       Assignments.Any(a => Persistable.ChangedValues.Exists(p => a.Target.Field == p.Value1));
            }
        }

        protected QueryList UpdateValues
        {
            get
            {
                return Assignments
                    .Select(a =>
                        {
                            var value = _keyManager.GetKeyVariable(TargetType, a);
                            return ColumnContainer<AssignmentColumn>.Column(a.Source.Field)
                                .Set(value == null
                                         ? (IQueryObject)
                                           QueryValue.Literal(Reflector.Read(Persistable.Instance,
                                                                             a.Target.Field))
                                         : Variable.Named(value));
                        }).ToQueryList();
            }
        }

        protected Where ConcurrentWhereClause
        {
            get
            {
                var clause = new CriteriaClause();
                SourceMap
                    .Assignments
                    .ForEach(a => clause.And(
                                      Criterion.Column(a.Source.Field, "")
                                          .EqualTo(QueryValue.Literal(Persistable.GetOriginalValue(a.Target.Field, Persistable)))
                                      ));

                return Where.Criteria(clause);
            }
        }

        protected Where SimpleWhereClause
        {
            get
            {
                var clause = new CriteriaClause();
                SourceMap
                    .Assignments
                    .Where(a => a.PrimaryKey)
                    .ForEach(a => clause.And(
                                      Criterion.Column(a.Source.Field, "")
                                          .EqualTo(
                                          QueryValue.Literal(Reflector.Read(Persistable.Instance, a.Target.Field)))
                                      ));

                return Where.Criteria(clause);
            }
        }

        public override IQueryObject Build(bool enforceConcurrency)
        {
            if (!HasUpdates)
                return InsertInstanceIntoKeyTable;

            var batch = _keyManager.GetVariableAssignments(TargetType, SourceMap, Persistable) +
                        QualifiedTableAction<Update, AssignmentColumn>.Query(Table.From(SourceMap.SourceName),
                                                                             UpdateValues,
                                                                             enforceConcurrency
                                                                                 ? ConcurrentWhereClause
                                                                                 : SimpleWhereClause
                            ).Outputs(OutputClause);

            if (enforceConcurrency)
                batch += ConcurrencyCheck;

            return batch;
        }

        public UpdateBuilder(ISourceMap sourceMap, IPersistenceKeyManager keyManager, IPersistable target,
                             IPersistable parent)
            : base(sourceMap, keyManager, target, parent)
        {
            _assignmentFilter = a =>
                                a.Persistable &&
                                !a.PrimaryKey &&
                                (a.Nullable || Reflector.Read(target.Instance, a.Target.Field) != null) &&
                                Persistable.ChangedValues.Any(v => v.Value1 == a.Target.Field);
        }
    }
}